// =============================================================================
//  Spangles Analyzer
//
//  Copyright(c) 2008
//  See LICENSE.txt for licensing information.
// =============================================================================

package com.google.spangles.domain;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * (Description)
 * (Any invariants [conditions that are always true] )
 * (Usage instructions, and/or examples)
 * (TODOs, ASSUMPTIONs, etc.)
 * (Known Bugs)
 * (Concurrency strategy - not thread safe or how it's thread safe)
 * 
 * @author Chris
 */
public class Symmetry
{
   private static final Location[] HEXAGON_TABLE = new Location[]
                                                                {
                                                                 new Location(0,0),
                                                                 new Location(0,1),
                                                                 new Location(1,1),
                                                                 new Location(2,1),
                                                                 new Location(2,0),
                                                                 new Location(1,0)                                                                 
                                                                };          // 4-(n-s)
   
   private static final Translation[] SKEW_TABLE = new Translation[]
                                                                   {
                                                                    new Translation(-1,-1),
                                                                    new Translation(1,-1),
                                                                    new Translation(2,0),
                                                                    new Translation(1,1),
                                                                    new Translation(-1,1),
                                                                    new Translation(-2,0)
                                                                   };       // 1-(n+s)
   
   private static final Translation[] OFFSET_TABLE = new Translation[]
                                                                     {
                                                                      new Translation(0,-2),
                                                                      new Translation(3,-1),
                                                                      new Translation(3,1),
                                                                      new Translation(0,2),
                                                                      new Translation(-3,1),
                                                                      new Translation(-3,-1)
                                                                     }; // -(n+s)
   
   int r;
   
   int s;
   
   /**
    * @param i
    * @param j
    */
   public Symmetry(int r, int s)
   {
      int rr = r%2;
      int ss = s%6;
      
      if(rr<0)
      {
         rr+=2;
      }
      if(ss<0)
      {
         ss+=6;
      }
      this.r=rr;
      this.s=ss;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param sj
    * @return
    */
   public Symmetry apply(Symmetry x)
   {
      int ar = this.r;
      int as = this.s;
      int br = x.r;
      int bs = x.s;
      
      if(br != 0)
      {
         ar++;
         as = - as;
      }
      return new Symmetry(ar, as+bs);
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public Symmetry inverse()
   {
      int ar = this.r;
      int as = this.s;
      
      if (ar == 1)
      {
         return this;
      }
      return new Symmetry(0, -as);
   }

   /* (non-Javadoc)
    * @see java.lang.Object#hashCode()
    */
   public int hashCode()
   {
      final int prime = 31;
      int result = 1;
      result = prime * result + r;
      result = prime * result + s;
      return result;
   }

   /* (non-Javadoc)
    * @see java.lang.Object#equals(java.lang.Object)
    */
   public boolean equals(Object obj)
   {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (getClass() != obj.getClass())
         return false;
      final Symmetry other = (Symmetry) obj;
      if (r != other.r)
         return false;
      if (s != other.s)
         return false;
      return true;
   }
   
   public String toString()
   {
      return "r"+r+"s"+s;
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param l
    * @return
    */
   public Location apply(Location l)
   {
      int x = l.getX();
      int y = l.getY();
      
      // first apply skew symmetry 0 to get x=0;
      int skews = x;
      x -= skews;
      y -= skews;
      
      // now apply translation symmetry 0 to get y=0 or 1
      int residue = (y & 1);
      y -= residue;
      int offsets = y/2;
      
      // lookup and transform the residue in the hexagon table 4-(n-s)
      int newResidue = residue - s;
      if (r!=0)
      {
         newResidue = 4 - newResidue;
      }
      newResidue %= 6;
      if(newResidue < 0)
      {
         newResidue += 6;
      }
      
      // lookup and transform the offset -(n+s)
      int newOffset = s;
      if (r!= 0)
      {
         newOffset = -newOffset;
      }
      newOffset %= 6;
      if (newOffset < 0)
      {
         newOffset += 6;
      }
      
      // lookup and transform the skew 1-(n+s)
      int newSkew = s;
      if (r!= 0)
      {
         newSkew = 1 - newSkew;
      }
      newSkew %= 6;
      if (newSkew < 0)
      {
         newSkew += 6;
      }
      
      Location n1 = HEXAGON_TABLE[newResidue];
      Translation tOffset = OFFSET_TABLE[newOffset];
      Translation tSkew = SKEW_TABLE[newSkew];
      
      Translation sOffset = tOffset.scale(-offsets);
      Translation sSkew = tSkew.scale(-skews);
      
      return sSkew.apply(sOffset.apply(n1));
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @param p0
    * @return
    */
   public CanonicalPosition apply(Position p0)
   {
      Map<Location, Color> l0 = p0.getLayout();
      Map<Location, Color> l1 = new HashMap<Location, Color>();
      
      for(Iterator<Entry<Location,Color>> ei = l0.entrySet().iterator(); ei.hasNext(); )
      {
         Entry<Location,Color> e = ei.next();
         Location l = e.getKey();
         Color c = e.getValue();
         
         l1.put(apply(l),c);         
      }
      
      Position p2 = new Position(p0.getToMove(), l1);
      return p2.canonicalize();
   }
}
